package com.zws.cucumber.spring.configuration;

import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
//import com.github.tomakehurst.wiremock.jetty9.JettyHttpServer;
import com.github.tomakehurst.wiremock.servlet.WireMockHandlerDispatchingServlet;
import com.zws.cucumber.ZwsConstants;
import org.apache.commons.collections4.map.HashedMap;
import org.objenesis.ObjenesisStd;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.test.context.event.AfterTestMethodEvent;
import org.springframework.test.context.event.BeforeTestMethodEvent;
import org.springframework.test.context.event.annotation.AfterTestMethod;
import org.springframework.test.context.event.annotation.BeforeTestMethod;
import org.springframework.util.ReflectionUtils;
//import wiremock.org.eclipse.jetty.server.LocalConnector;
//import wiremock.org.eclipse.jetty.server.Server;
//import wiremock.org.eclipse.jetty.server.handler.HandlerCollection;
//import wiremock.org.eclipse.jetty.server.handler.gzip.GzipHandler;
//import wiremock.org.eclipse.jetty.servlet.ServletContextHandler;
import com.zws.cucumber.boostrap.ZwsWiremockBootstrap;
import com.zws.cucumber.optimization.ZwsOptimizations;
//import com.zws.cucumber.optimization.socket.connector.ZwsCallerExecutionWiremockConnector;
//import com.zws.cucumber.optimization.socket.connector.ZwsConnectorWiremockAdaptor;
//import com.zws.cucumber.optimization.socket.oio.ZwsSocketImplAdapter;
//import com.zws.cucumber.optimization.socket.wiremock.ZwsSocketFaultInjectorFactory;
import com.zws.cucumber.util.ZwsMockUtil;
//import com.zws.cucumber.wiremock.ZwsWiremockServerProxy;

import javax.annotation.PostConstruct;
import java.lang.reflect.Field;
import java.util.Map;

/**
 * author: zws
 */
@Configuration
public class ZwsWiremockConf implements EnvironmentAware {

    private static final Logger logger = LoggerFactory.getLogger(ZwsWiremockConf.class);

    @Value("${ct.cucumber.wiremock.clearEachCase:true}")
    private boolean clearEachCase;

    private Environment env;

    private static volatile WireMockServer staticWireMockServer;

    public static WireMockServer getStaticWireMockServer() {
        return staticWireMockServer;
    }

    @PostConstruct
    public void init() {
        logger.info("init ZwsWiremockConf");
        staticWireMockServer = ZwsWiremockBootstrap.getWireMockServer();

        // system properties
        System.setProperty("wiremock.port", Integer.toString(staticWireMockServer.port()));
        System.setProperty("wiremock.unreachablePort", Integer.toString(ZwsConstants.WIREMOCK_UNREACHABLE_PORT));
        // env
        Map<String, Object> map = new HashedMap<>();
        map.put("wiremock.port", Integer.toString(staticWireMockServer.port()));
        map.put("wiremock.unreachablePort", Integer.toString(ZwsConstants.WIREMOCK_UNREACHABLE_PORT));
        ((ConfigurableEnvironment) env).getPropertySources().addFirst(new MapPropertySource("wiremockProperties", map));

//        addLocalConnector(staticWireMockServer);
    }

    @Bean(destroyMethod = "stop")
    public WireMockServer wireMockServer(ConfigurableEnvironment environment) {
//        ZwsWiremockServerProxy wireMockServer = new ObjenesisStd().newInstance(ZwsWiremockServerProxy.class);
//        ZwsCommonConf.asyncInitResource(() -> {
//            WireMockServer puppet = ZwsWiremockBootstrap.getWireMockServer();
//
//            // system properties
//            System.setProperty("wiremock.port", Integer.toString(puppet.port()));
//            System.setProperty("wiremock.unreachablePort", Integer.toString(ZwsConstants.WIREMOCK_UNREACHABLE_PORT));
//            // env
//            Map<String, Object> map = new HashedMap<>();
//            map.put("wiremock.port", Integer.toString(puppet.port()));
//            map.put("wiremock.unreachablePort", Integer.toString(ZwsConstants.WIREMOCK_UNREACHABLE_PORT));
//            environment.getPropertySources().addFirst(new MapPropertySource("wiremockProperties", map));
//
//            addLocalConnector(puppet);
//            wireMockServer.setPuppet(puppet);
//        });
//        staticWireMockServer = wireMockServer;
//        return wireMockServer;

        return staticWireMockServer;
    }

    // ugly code here
//    private void addLocalConnector(WireMockServer wireMockServer) {
//        try {
//            // add LocalConnector.
//            Field fieldOfHttpServer = ReflectionUtils.findField(WireMockServer.class, "httpServer");
//            fieldOfHttpServer.setAccessible(true);
//            JettyHttpServer httpServer = (JettyHttpServer) ReflectionUtils.getField(fieldOfHttpServer, wireMockServer);
//
//            Field fieldOfJettyServer = ReflectionUtils.findField(JettyHttpServer.class, "jettyServer");
//            fieldOfJettyServer.setAccessible(true);
//            Server jettyServer = (Server) ReflectionUtils.getField(fieldOfJettyServer, httpServer);
//
//            ZwsConnectorWiremockAdaptor connectorAdaptor;
//            if (ZwsOptimizations.isJettyExecutionInMainThreadEnabled()) {
//                LocalConnector localConnector = new LocalConnector(jettyServer);
//                localConnector.start();
//                jettyServer.addConnector(localConnector);
//
//                ZwsCallerExecutionWiremockConnector executionInMainThreadLocalConnector = new ZwsCallerExecutionWiremockConnector(jettyServer);
//                executionInMainThreadLocalConnector.start();
//                jettyServer.addConnector(executionInMainThreadLocalConnector);
//
//                connectorAdaptor = new ZwsConnectorWiremockAdaptor(localConnector, executionInMainThreadLocalConnector, wireMockServer);
////                connectorAdaptor.setAcceptorRunnable(localConnector.getAcceptorRunnable());
//
//            } else {
//                LocalConnector localConnector = new LocalConnector(jettyServer);
//                localConnector.start();
//                jettyServer.addConnector(localConnector);
//
//                connectorAdaptor = new ZwsConnectorWiremockAdaptor(localConnector, wireMockServer);
//            }
//
//            // reset FaultInjectorFactory
//            HandlerCollection handlerCollection = (HandlerCollection) jettyServer.getHandler();
//            GzipHandler gzipHandler = handlerCollection.getBean(GzipHandler.class);
//            ServletContextHandler servletContextHandler = (ServletContextHandler) gzipHandler.getHandler();
//            WireMockHandlerDispatchingServlet servlet = (WireMockHandlerDispatchingServlet) servletContextHandler.getServletHandler().getServlets(WireMockHandlerDispatchingServlet.class).get(0).getServlet();
//
//            Field fieldOfFaultHandlerFactory = ReflectionUtils.findField(WireMockHandlerDispatchingServlet.class, "faultHandlerFactory");
//            fieldOfFaultHandlerFactory.setAccessible(true);
//            ReflectionUtils.setField(fieldOfFaultHandlerFactory, servlet, new ZwsSocketFaultInjectorFactory());
//
//            ZwsSocketImplAdapter.registerLocalConnectorAdaptor(wireMockServer.port(), connectorAdaptor);
//        } catch (Exception e) {
//            throw new RuntimeException(e);
//        }
//    }

    @Bean(destroyMethod = "")
    @DependsOn({"wireMockServer","syncOnResourceAsyncInitPhase"})
    public WireMock wireMockClient(WireMockServer wireMockServer) {
        System.out.printf("port for WireMock: %d\n", wireMockServer.port());
        return new WireMock(wireMockServer.port());
    }

    @BeforeTestMethod()
    public void beforeTestMethod(BeforeTestMethodEvent event) {
        if (clearEachCase) {
            ApplicationContext applicationContext = event.getTestContext().getApplicationContext();
            WireMockServer wireMockServer = (WireMockServer) applicationContext.getBean("wireMockServer");
//            wireMockClient.resetMappings();

            wireMockServer.resetMappings();
            wireMockServer.resetRequests();
            ZwsMockUtil.resetMockedTimeout();
            //

        }
    }

    @AfterTestMethod()
    public void afterTestMethod(AfterTestMethodEvent event) {
        if (clearEachCase) {
            ApplicationContext applicationContext = event.getTestContext().getApplicationContext();
            WireMockServer wireMockServer = (WireMockServer) applicationContext.getBean("wireMockServer");
//            wireMockClient.resetMappings();

            wireMockServer.resetMappings();
            wireMockServer.resetRequests();
            ZwsMockUtil.resetMockedTimeout();
        }
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.env = environment;
    }
}
